package com.xiyuan.smartutils.client.http;

import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.constants.HttpConstants;

/**
 * URI信息，这里只做http协议解析 URI [scheme://host[:port]]/path[;jsessionid][?query]
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */
public class URI implements HttpConstants {
    private static final String DOMAIN = "_-." + _LETTERS_DIGITS_;
    private String sessionKey = "jsessionid";
    private String uri;
    private String scheme = "";
    private String host = "";
    private int port;
    private String path = "";
    private String sessionId;
    private String query = "";
    private String hash = "";
    
    public URI() {
        this.uri = _EMPTY_;
    }
    
    public URI(String url) {
        this();
        this.parseUri(url);
    }
    
    public String toString() {
        return uri;
    }
    
    public String getHash() {
        return hash;
    }
    
    public String getScheme() {
        return scheme;
    }
    
    public String getSessionKey() {
        return sessionKey;
    }
    
    /*** 设置sessionKey字段名，此方法必须在parseUri()前调用，否则无效 */
    public void setSessionKey(String sessionKey) {
        this.sessionKey = sessionKey;
    }
    
    public String getHost() {
        return host;
    }
    
    public int getPort() {
        return port;
    }
    
    public String getPath() {
        return this.path;
    }
    
    public String getSessionId() {
        return sessionId;
    }
    
    public String getQuery() {
        return query;
    }
    
    /**
     * 从path中解释虚拟目录
     */
    public String getVirtualDirectory() {
        int ind = path.indexOf('/', 1);
        if (ind != -1) {
            // 虚拟目录 如 /abc/test.html
            return path.substring(0, ind);
        }
        else {
            // 根目录, 如 /abc.html
            return "/";
        }
    }
    
    /**
     * 获取服务端根地址，不含最后的斜杠
     */
    public String getRootUrl() {
        boolean isHttps = isHttps();
        if ((isHttps && port == 443) || (!isHttps && port == 80)) {
            return scheme + "://" + host;
        }
        else {
            return scheme + "://" + host + ":" + port;
        }
    }
    
    /**
     * 获取服务端根地址，含最后的斜杠
     */
    public String getRootUrlSlash() {
        return StrUtils.addEndsWith(getRootUrl(), "/");
    }
    
    public boolean isHttp() {
        return _HTTP_.equalsIgnoreCase(scheme);
    }
    
    public boolean isHttps() {
        return _HTTPS_.equalsIgnoreCase(scheme);
    }
    
    public boolean isWs() {
        return _WS_.equalsIgnoreCase(scheme);
    }
    
    public boolean isWss() {
        return _WSS_.equalsIgnoreCase(scheme);
    }
    
    /**
     * 解析REQUEST-URI信息
     *
     * @param uri [scheme://host[:port]]/path[;jsessionid][?query]
     * @return =true 表示成功，=false 表示失败
     */
    public URI parseUri(String uri) {
        uri = StrUtils.trim(uri);
        if (this.uri.equals(uri)) {
            // 同一个地址
            return this;
        }
        
        if (Validates.isBlank(uri)) {return this;}
        
        this.uri = uri;
        
        // 1.解析协议
        int schI = uri.indexOf("://");
        if (schI > 1) {
            // 存在协议头
            scheme = uri.substring(0, schI).toLowerCase();
            // uri = uri.substring(schI + 3);
        }
        schI = uri.indexOf("//");
        if (scheme.length() + (scheme.isEmpty() ? 0 : scheme.length()) == schI) {
            uri = uri.substring(schI + 2);
        }
        
        // 2. 解析域名域名
        boolean hasHost = true;
        // 读取绝对路径时提供的host
        // 域名只能包括汉字+26个英文字母、“0-9”十个数字和“.-”。“.-”英文中的连接号，但开头和结尾不能含有并且不能连续出现
        StringBuilder strb = new StringBuilder(100);
        for (int i = 0; i < uri.length(); i++) {
            char c = uri.charAt(i);
            
            if (c == ':' || c == '?' || c == '/' || c == '#' || c == '&') {
                break;// 域名结束符
            }
            
            // 判断域名字符支持
            if (!(strb.length() == 0 || (Validates.isCharInString(c, DOMAIN) || (c >= '\u4E00' && c <= '\u9FD5')))) {
                // 域名字符不支持
                hasHost = false;
                break;
            }
            
            // host未结束
            strb.append(c);
            if (!(c == '.' || c == '-') || i == 0) {
                continue; // 当前字符不是连接符
            }
            c = uri.charAt(i - 1);
            if (!(c == '.' || c == '-')) {
                continue;// 上字符不是连接符
            }
            
            // 发现两个相连的连接符
            
            // 错误主机地址
            hasHost = false;
            break;// 连接符号不能同出现
        }
        
        // 检查收尾不能出现 “.-”
        strb.trimToSize();
        if (strb.length() < 2) {hasHost = false;}
        
        if (hasHost) {
            
            // 有主机地址
            String sp = ".-";
            char c1 = strb.charAt(0);
            char c2 = strb.charAt(strb.length() - 1);
            
            // 检查收尾是否为连接符
            if (sp.indexOf(c1) != -1 || sp.indexOf(c2) != -1) {hasHost = false;}
            
            // 检查不能单纯出现字母汉字或者‘-’连接符
            // if (hasHost && strb.indexOf(".") == -1) {hasHost = false;}
            
            uri = uri.substring(strb.length());
            if (hasHost) {
                // 解析到主机地址
                this.host = StrUtils.trim(strb.toString().toLowerCase());
            }
        }
        
        // 解析端口
        if (hasHost && uri.startsWith(":")) {
            // 解析到主机的情况下，解析端口
            strb.setLength(0);
            for (int i = 1; i < uri.length(); i++) {
                char c = uri.charAt(i);
                if ((strb.length() == 0 && Validates.isWhitespace(c)) || Validates.isNumeric(c)) {
                    // 第一个字符为空白或者 之后为数字
                    strb.append(c);
                    continue;// port未结束
                }
                
                if (!(c == '?' || c == '/' || c == '#' || c == '&')) {
                    strb.setLength(0);
                    // 非port结束内容
                }
                
                break;// port结束
            }
            
            if (strb.length() > 8) {
                // 不支持的数字
                strb.setLength(0);
            }
            
            if (strb.length() > 0) {
                this.port = Integer.parseInt(StrUtils.trim(strb.toString()));
                uri = uri.substring(strb.length() + 1);
            }
        }

//        if (this.host != null && this.port <= 0)// 找到端口
//            this.port = isHttps() || this.is ? 443 : 80;// 解析不到数字端口
        
        // 解析path
        if (uri.length() > 0) {
            strb.setLength(0);
            for (int i = 0; i < uri.length(); i++) {
                char c = uri.charAt(i);
                if (c == '?' || c == '#' || c == '&') {
                    break;// path结束
                }
                
                strb.append(c);
            }
            
            this.path = StrUtils.trim(strb.toString());
            uri = StrUtils.trim(uri.substring(strb.length()));
        }
        
        // 解析session信息
        if (!uri.isEmpty()) {
            
            int i = -1;
            int frmIndex = 0;
            while ((i = uri.indexOf(sessionKey, frmIndex)) != -1) {
                boolean flag = false;
                if (i > 0) {
                    
                    int j = i - 1;
                    // 寻找sessionKey开始位置
                    for (; j > 0; j--) {
                        char c = uri.charAt(j);
                        if (Validates.isWhitespace(c)) {continue;}
                        
                        if (c == '?' || c == '#' || c == '&' || c == ';') {
                            // sessionKey 前面分割符
                            flag = true;
                            break;
                        }
                        else {break;}
                    }
                    
                    // 没找到sessionKey开始位置
                    if (!flag) {
                        frmIndex += sessionKey.length();
                        if (frmIndex >= uri.length()) {break;}
                        
                        continue;
                    }
                    // sessionKey 前一个分割符
                    int j2 = j;
                    // 查找到sessionKey ‘=’位置
                    j = i + sessionKey.length();
                    flag = false;
                    for (; j < uri.length(); j++) {
                        char c = uri.charAt(j);
                        if (Validates.isWhitespace(c)) {continue;}
                        
                        if (c == '=') {
                            flag = true;
                            break;
                        }
                        else {break;}
                    }
                    
                    // 没找到sessionKey开始位置
                    if (!flag) {
                        frmIndex = j;
                        if (frmIndex >= uri.length()) {break;}
                        
                        continue;
                    }
                    
                    // 找到 ‘=’ 并查询session值
                    strb.setLength(0);
                    for (j++; j < uri.length(); j++) {
                        char c = uri.charAt(j);
                        if (strb.length() == 0 && Validates.isWhitespace(c)) {continue;}
                        
                        if (Validates.isCharInString(c, _LETTERS_DIGITS_)) {
                            strb.append(c);
                            continue;
                        }
                        
                        if (c == '?' || c == '#' || c == ';' || c == '&') {
                            break;// session值结束
                        }
                        else {
                            strb.setLength(0);
                            break;
                        }
                    }
                    
                    if (strb.length() == 0) {
                        // 没找到session值
                        frmIndex = j;
                        if (frmIndex >= uri.length()) {break;}
                        continue;
                    }
                    
                    this.sessionId = strb.toString();
                    String t = "";
                    if (j2 > -1) {t = uri.substring(0, j2);}
                    if (j < uri.length()) {uri = t + uri.substring(j);}
                    else {uri = t;}
                    
                    break;
                }
            }
        }
        
        if ((schI = uri.indexOf("#")) != -1) {
            this.hash = StrUtils.trim(uri.substring(schI + 1));
            uri = uri.substring(0, schI);
        }
        this.query = StrUtils.trim(uri);
        this.query = StrUtils.trimLeft(this.query, "/");
        this.query = StrUtils.trimLeft(this.query, "?");
        this.query = StrUtils.trimLeft(this.query, "&");
        this.path = this.path == null ? "/" : StrUtils.addStartsWith(StrUtils.trimLeft(path, ":"), "/");
        
        
        return this;
    }
    
}
